/**
 * zrender: 图形空间辅助类
 *
 * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
 *         pissang (https://www.github.com/pissang)
 *
 * isInside：是否在区域内部
 * isOutside：是否在区域外部
 * getTextWidth：测算单行文本宽度
 */


'use strict';
var util = require('./util');
var curve = require('./curve');
var _ctx;
var _textWidthCache = {};
var _textHeightCache = {};
var _textWidthCacheCounter = 0;
var _textHeightCacheCounter = 0;
var TEXT_CACHE_MAX = 5000;
var PI2 = Math.PI * 2;
function normalizeRadian(angle) {
    angle %= PI2;
    if (angle < 0) {
        angle += PI2;
    }
    return angle;
}
/**
         * 包含判断
         *
         * @param {Object} shape : 图形
         * @param {Object} area ： 目标区域
         * @param {number} x ： 横坐标
         * @param {number} y ： 纵坐标
         */
function isInside(shape, area, x, y) {
    if (!area || !shape) {
        // 无参数或不支持类型
        return false;
    }
    var zoneType = shape.type;
    _ctx = _ctx || util.getContext();
    // 未实现或不可用时(excanvas不支持)则数学运算，主要是line，polyline，ring
    var _mathReturn = _mathMethod(shape, area, x, y);
    if (typeof _mathReturn != 'undefined') {
        return _mathReturn;
    }
    if (shape.buildPath && _ctx.isPointInPath) {
        return _buildPathMethod(shape, _ctx, area, x, y);
    }
    // 上面的方法都行不通时
    switch (zoneType) {
    case 'ellipse':
        // Todo，不精确
        return true;
    // 旋轮曲线  不准确
    case 'trochoid':
        var _r = area.location == 'out' ? area.r1 + area.r2 + area.d : area.r1 - area.r2 + area.d;
        return isInsideCircle(area, x, y, _r);
    // 玫瑰线 不准确
    case 'rose':
        return isInsideCircle(area, x, y, area.maxr);
    // 路径，椭圆，曲线等-----------------13
    default:
        return false;    // Todo，暂不支持
    }
}
/**
         * @param {Object} shape : 图形
         * @param {Object} area ：目标区域
         * @param {number} x ： 横坐标
         * @param {number} y ： 纵坐标
         * @return {boolean=} true表示坐标处在图形中
         */
function _mathMethod(shape, area, x, y) {
    var zoneType = shape.type;
    // 在矩形内则部分图形需要进一步判断
    switch (zoneType) {
    // 贝塞尔曲线
    case 'bezier-curve':
        if (typeof area.cpX2 === 'undefined') {
            return isInsideQuadraticStroke(area.xStart, area.yStart, area.cpX1, area.cpY1, area.xEnd, area.yEnd, area.lineWidth, x, y);
        }
        return isInsideCubicStroke(area.xStart, area.yStart, area.cpX1, area.cpY1, area.cpX2, area.cpY2, area.xEnd, area.yEnd, area.lineWidth, x, y);
    // 线
    case 'line':
        return isInsideLine(area.xStart, area.yStart, area.xEnd, area.yEnd, area.lineWidth, x, y);
    // 折线
    case 'polyline':
        return isInsidePolyline(area.pointList, area.lineWidth, x, y);
    // 圆环
    case 'ring':
        return isInsideRing(area.x, area.y, area.r0, area.r, x, y);
    // 圆形
    case 'circle':
        return isInsideCircle(area.x, area.y, area.r, x, y);
    // 扇形
    case 'sector':
        var startAngle = area.startAngle * Math.PI / 180;
        var endAngle = area.endAngle * Math.PI / 180;
        if (!area.clockWise) {
            startAngle = -startAngle;
            endAngle = -endAngle;
        }
        return isInsideSector(area.x, area.y, area.r0, area.r, startAngle, endAngle, !area.clockWise, x, y);
    // 多边形
    case 'path':
        return area.pathArray && isInsidePath(area.pathArray, Math.max(area.lineWidth, 5), area.brushType, x, y);
    case 'polygon':
    case 'star':
    case 'isogon':
        return isInsidePolygon(area.pointList, x, y);
    // 文本
    case 'text':
        var rect = area.__rect || shape.getRect(area);
        return isInsideRect(rect.x, rect.y, rect.width, rect.height, x, y);
    // 矩形
    case 'rectangle':
    // 图片
    case 'image':
        return isInsideRect(area.x, area.y, area.width, area.height, x, y);
    }
}
/**
         * 通过buildPath方法来判断，三个方法中较快，但是不支持线条类型的shape，
         * 而且excanvas不支持isPointInPath方法
         *
         * @param {Object} shape ： shape
         * @param {Object} context : 上下文
         * @param {Object} area ：目标区域
         * @param {number} x ： 横坐标
         * @param {number} y ： 纵坐标
         * @return {boolean} true表示坐标处在图形中
         */
function _buildPathMethod(shape, context, area, x, y) {
    // 图形类实现路径创建了则用类的path
    context.beginPath();
    shape.buildPath(context, area);
    context.closePath();
    return context.isPointInPath(x, y);
}
/**
         * !isInside
         */
function isOutside(shape, area, x, y) {
    return !isInside(shape, area, x, y);
}
/**
         * 线段包含判断
         * @param  {number}  x0
         * @param  {number}  y0
         * @param  {number}  x1
         * @param  {number}  y1
         * @param  {number}  lineWidth
         * @param  {number}  x
         * @param  {number}  y
         * @return {boolean}
         */
function isInsideLine(x0, y0, x1, y1, lineWidth, x, y) {
    if (lineWidth === 0) {
        return false;
    }
    var _l = Math.max(lineWidth, 5);
    var _a = 0;
    var _b = x0;
    // Quick reject
    if (y > y0 + _l && y > y1 + _l || y < y0 - _l && y < y1 - _l || x > x0 + _l && x > x1 + _l || x < x0 - _l && x < x1 - _l) {
        return false;
    }
    if (x0 !== x1) {
        _a = (y0 - y1) / (x0 - x1);
        _b = (x0 * y1 - x1 * y0) / (x0 - x1);
    } else {
        return Math.abs(x - x0) <= _l / 2;
    }
    var tmp = _a * x - y + _b;
    var _s = tmp * tmp / (_a * _a + 1);
    return _s <= _l / 2 * _l / 2;
}
/**
         * 三次贝塞尔曲线描边包含判断
         * @param  {number}  x0
         * @param  {number}  y0
         * @param  {number}  x1
         * @param  {number}  y1
         * @param  {number}  x2
         * @param  {number}  y2
         * @param  {number}  x3
         * @param  {number}  y3
         * @param  {number}  lineWidth
         * @param  {number}  x
         * @param  {number}  y
         * @return {boolean}
         */
function isInsideCubicStroke(x0, y0, x1, y1, x2, y2, x3, y3, lineWidth, x, y) {
    if (lineWidth === 0) {
        return false;
    }
    var _l = Math.max(lineWidth, 5);
    // Quick reject
    if (y > y0 + _l && y > y1 + _l && y > y2 + _l && y > y3 + _l || y < y0 - _l && y < y1 - _l && y < y2 - _l && y < y3 - _l || x > x0 + _l && x > x1 + _l && x > x2 + _l && x > x3 + _l || x < x0 - _l && x < x1 - _l && x < x2 - _l && x < x3 - _l) {
        return false;
    }
    var d = curve.cubicProjectPoint(x0, y0, x1, y1, x2, y2, x3, y3, x, y, null);
    return d <= _l / 2;
}
/**
         * 二次贝塞尔曲线描边包含判断
         * @param  {number}  x0
         * @param  {number}  y0
         * @param  {number}  x1
         * @param  {number}  y1
         * @param  {number}  x2
         * @param  {number}  y2
         * @param  {number}  lineWidth
         * @param  {number}  x
         * @param  {number}  y
         * @return {boolean}
         */
function isInsideQuadraticStroke(x0, y0, x1, y1, x2, y2, lineWidth, x, y) {
    if (lineWidth === 0) {
        return false;
    }
    var _l = Math.max(lineWidth, 5);
    // Quick reject
    if (y > y0 + _l && y > y1 + _l && y > y2 + _l || y < y0 - _l && y < y1 - _l && y < y2 - _l || x > x0 + _l && x > x1 + _l && x > x2 + _l || x < x0 - _l && x < x1 - _l && x < x2 - _l) {
        return false;
    }
    var d = curve.quadraticProjectPoint(x0, y0, x1, y1, x2, y2, x, y, null);
    return d <= _l / 2;
}
/**
         * 圆弧描边包含判断
         * @param  {number}  cx
         * @param  {number}  cy
         * @param  {number}  r
         * @param  {number}  startAngle
         * @param  {number}  endAngle
         * @param  {boolean}  anticlockwise
         * @param  {number} lineWidth
         * @param  {number}  x
         * @param  {number}  y
         * @return {Boolean}
         */
function isInsideArcStroke(cx, cy, r, startAngle, endAngle, anticlockwise, lineWidth, x, y) {
    if (lineWidth === 0) {
        return false;
    }
    var _l = Math.max(lineWidth, 5);
    x -= cx;
    y -= cy;
    var d = Math.sqrt(x * x + y * y);
    if (d - _l > r || d + _l < r) {
        return false;
    }
    if (Math.abs(startAngle - endAngle) >= PI2) {
        // Is a circle
        return true;
    }
    if (anticlockwise) {
        var tmp = startAngle;
        startAngle = normalizeRadian(endAngle);
        endAngle = normalizeRadian(tmp);
    } else {
        startAngle = normalizeRadian(startAngle);
        endAngle = normalizeRadian(endAngle);
    }
    if (startAngle > endAngle) {
        endAngle += PI2;
    }
    var angle = Math.atan2(y, x);
    if (angle < 0) {
        angle += PI2;
    }
    return angle >= startAngle && angle <= endAngle || angle + PI2 >= startAngle && angle + PI2 <= endAngle;
}
function isInsidePolyline(points, lineWidth, x, y) {
    var lineWidth = Math.max(lineWidth, 10);
    for (var i = 0, l = points.length - 1; i < l; i++) {
        var x0 = points[i][0];
        var y0 = points[i][1];
        var x1 = points[i + 1][0];
        var y1 = points[i + 1][1];
        if (isInsideLine(x0, y0, x1, y1, lineWidth, x, y)) {
            return true;
        }
    }
    return false;
}
function isInsideRing(cx, cy, r0, r, x, y) {
    var d = (x - cx) * (x - cx) + (y - cy) * (y - cy);
    return d < r * r && d > r0 * r0;
}
/**
         * 矩形包含判断
         */
function isInsideRect(x0, y0, width, height, x, y) {
    return x >= x0 && x <= x0 + width && y >= y0 && y <= y0 + height;
}
/**
         * 圆形包含判断
         */
function isInsideCircle(x0, y0, r, x, y) {
    return (x - x0) * (x - x0) + (y - y0) * (y - y0) < r * r;
}
/**
         * 扇形包含判断
         */
function isInsideSector(cx, cy, r0, r, startAngle, endAngle, anticlockwise, x, y) {
    return isInsideArcStroke(cx, cy, (r0 + r) / 2, startAngle, endAngle, anticlockwise, r - r0, x, y);
}
/**
         * 多边形包含判断
         * 与 canvas 一样采用 non-zero winding rule
         */
function isInsidePolygon(points, x, y) {
    var N = points.length;
    var w = 0;
    for (var i = 0, j = N - 1; i < N; i++) {
        var x0 = points[j][0];
        var y0 = points[j][1];
        var x1 = points[i][0];
        var y1 = points[i][1];
        w += windingLine(x0, y0, x1, y1, x, y);
        j = i;
    }
    return w !== 0;
}
function windingLine(x0, y0, x1, y1, x, y) {
    if (y > y0 && y > y1 || y < y0 && y < y1) {
        return 0;
    }
    if (y1 == y0) {
        return 0;
    }
    var dir = y1 < y0 ? 1 : -1;
    var t = (y - y0) / (y1 - y0);
    var x_ = t * (x1 - x0) + x0;
    return x_ > x ? dir : 0;
}
// 临时数组
var roots = [
    -1,
    -1,
    -1
];
var extrema = [
    -1,
    -1
];
function swapExtrema() {
    var tmp = extrema[0];
    extrema[0] = extrema[1];
    extrema[1] = tmp;
}
function windingCubic(x0, y0, x1, y1, x2, y2, x3, y3, x, y) {
    // Quick reject
    if (y > y0 && y > y1 && y > y2 && y > y3 || y < y0 && y < y1 && y < y2 && y < y3) {
        return 0;
    }
    var nRoots = curve.cubicRootAt(y0, y1, y2, y3, y, roots);
    if (nRoots === 0) {
        return 0;
    } else {
        var w = 0;
        var nExtrema = -1;
        var y0_, y1_;
        for (var i = 0; i < nRoots; i++) {
            var t = roots[i];
            var x_ = curve.cubicAt(x0, x1, x2, x3, t);
            if (x_ < x) {
                // Quick reject
                continue;
            }
            if (nExtrema < 0) {
                nExtrema = curve.cubicExtrema(y0, y1, y2, y3, extrema);
                if (extrema[1] < extrema[0] && nExtrema > 1) {
                    swapExtrema();
                }
                y0_ = curve.cubicAt(y0, y1, y2, y3, extrema[0]);
                if (nExtrema > 1) {
                    y1_ = curve.cubicAt(y0, y1, y2, y3, extrema[1]);
                }
            }
            if (nExtrema == 2) {
                // 分成三段单调函数
                if (t < extrema[0]) {
                    w += y0_ < y0 ? 1 : -1;
                } else if (t < extrema[1]) {
                    w += y1_ < y0_ ? 1 : -1;
                } else {
                    w += y3 < y1_ ? 1 : -1;
                }
            } else {
                // 分成两段单调函数
                if (t < extrema[0]) {
                    w += y0_ < y0 ? 1 : -1;
                } else {
                    w += y3 < y0_ ? 1 : -1;
                }
            }
        }
        return w;
    }
}
function windingQuadratic(x0, y0, x1, y1, x2, y2, x, y) {
    // Quick reject
    if (y > y0 && y > y1 && y > y2 || y < y0 && y < y1 && y < y2) {
        return 0;
    }
    var nRoots = curve.quadraticRootAt(y0, y1, y2, y, roots);
    if (nRoots === 0) {
        return 0;
    } else {
        var t = curve.quadraticExtremum(y0, y1, y2);
        if (t >= 0 && t <= 1) {
            var w = 0;
            var y_ = curve.quadraticAt(y0, y1, y2, t);
            for (var i = 0; i < nRoots; i++) {
                var x_ = curve.quadraticAt(x0, x1, x2, roots[i]);
                if (x_ < x) {
                    continue;
                }
                if (roots[i] < t) {
                    w += y_ < y0 ? 1 : -1;
                } else {
                    w += y2 < y_ ? 1 : -1;
                }
            }
            return w;
        } else {
            var x_ = curve.quadraticAt(x0, x1, x2, roots[0]);
            if (x_ < x) {
                return 0;
            }
            return y2 < y0 ? 1 : -1;
        }
    }
}
// TODO
// Arc 旋转
function windingArc(cx, cy, r, startAngle, endAngle, anticlockwise, x, y) {
    y -= cy;
    if (y > r || y < -r) {
        return 0;
    }
    var tmp = Math.sqrt(r * r - y * y);
    roots[0] = -tmp;
    roots[1] = tmp;
    if (Math.abs(startAngle - endAngle) >= PI2) {
        // Is a circle
        startAngle = 0;
        endAngle = PI2;
        var dir = anticlockwise ? 1 : -1;
        if (x >= roots[0] + cx && x <= roots[1] + cx) {
            return dir;
        } else {
            return 0;
        }
    }
    if (anticlockwise) {
        var tmp = startAngle;
        startAngle = normalizeRadian(endAngle);
        endAngle = normalizeRadian(tmp);
    } else {
        startAngle = normalizeRadian(startAngle);
        endAngle = normalizeRadian(endAngle);
    }
    if (startAngle > endAngle) {
        endAngle += PI2;
    }
    var w = 0;
    for (var i = 0; i < 2; i++) {
        var x_ = roots[i];
        if (x_ + cx > x) {
            var angle = Math.atan2(y, x_);
            var dir = anticlockwise ? 1 : -1;
            if (angle < 0) {
                angle = PI2 + angle;
            }
            if (angle >= startAngle && angle <= endAngle || angle + PI2 >= startAngle && angle + PI2 <= endAngle) {
                if (angle > Math.PI / 2 && angle < Math.PI * 1.5) {
                    dir = -dir;
                }
                w += dir;
            }
        }
    }
    return w;
}
/**
         * 路径包含判断
         * 与 canvas 一样采用 non-zero winding rule
         */
function isInsidePath(pathArray, lineWidth, brushType, x, y) {
    var w = 0;
    var xi = 0;
    var yi = 0;
    var x0 = 0;
    var y0 = 0;
    var beginSubpath = true;
    var firstCmd = true;
    brushType = brushType || 'fill';
    var hasStroke = brushType === 'stroke' || brushType === 'both';
    var hasFill = brushType === 'fill' || brushType === 'both';
    // var roots = [-1, -1, -1];
    for (var i = 0; i < pathArray.length; i++) {
        var seg = pathArray[i];
        var p = seg.points;
        // Begin a new subpath
        if (beginSubpath || seg.command === 'M') {
            if (i > 0) {
                // Close previous subpath
                if (hasFill) {
                    w += windingLine(xi, yi, x0, y0, x, y);
                }
                if (w !== 0) {
                    return true;
                }
            }
            x0 = p[p.length - 2];
            y0 = p[p.length - 1];
            beginSubpath = false;
            if (firstCmd && seg.command !== 'A') {
                // 如果第一个命令不是M, 是lineTo, bezierCurveTo
                // 等绘制命令的话，是会从该绘制的起点开始算的
                // Arc 会在之后做单独处理所以这里忽略
                firstCmd = false;
                xi = x0;
                yi = y0;
            }
        }
        switch (seg.command) {
        case 'M':
            xi = p[0];
            yi = p[1];
            break;
        case 'L':
            if (hasStroke) {
                if (isInsideLine(xi, yi, p[0], p[1], lineWidth, x, y)) {
                    return true;
                }
            }
            if (hasFill) {
                w += windingLine(xi, yi, p[0], p[1], x, y);
            }
            xi = p[0];
            yi = p[1];
            break;
        case 'C':
            if (hasStroke) {
                if (isInsideCubicStroke(xi, yi, p[0], p[1], p[2], p[3], p[4], p[5], lineWidth, x, y)) {
                    return true;
                }
            }
            if (hasFill) {
                w += windingCubic(xi, yi, p[0], p[1], p[2], p[3], p[4], p[5], x, y);
            }
            xi = p[4];
            yi = p[5];
            break;
        case 'Q':
            if (hasStroke) {
                if (isInsideQuadraticStroke(xi, yi, p[0], p[1], p[2], p[3], lineWidth, x, y)) {
                    return true;
                }
            }
            if (hasFill) {
                w += windingQuadratic(xi, yi, p[0], p[1], p[2], p[3], x, y);
            }
            xi = p[2];
            yi = p[3];
            break;
        case 'A':
            // TODO Arc 旋转
            // TODO Arc 判断的开销比较大
            var cx = p[0];
            var cy = p[1];
            var rx = p[2];
            var ry = p[3];
            var theta = p[4];
            var dTheta = p[5];
            var x1 = Math.cos(theta) * rx + cx;
            var y1 = Math.sin(theta) * ry + cy;
            // 不是直接使用 arc 命令
            if (!firstCmd) {
                w += windingLine(xi, yi, x1, y1);
            } else {
                firstCmd = false;
                // 第一个命令起点还未定义
                x0 = x1;
                y0 = y1;
            }
            // zr 使用scale来模拟椭圆, 这里也对x做一定的缩放
            var _x = (x - cx) * ry / rx + cx;
            if (hasStroke) {
                if (isInsideArcStroke(cx, cy, ry, theta, theta + dTheta, 1 - p[7], lineWidth, _x, y)) {
                    return true;
                }
            }
            if (hasFill) {
                w += windingArc(cx, cy, ry, theta, theta + dTheta, 1 - p[7], _x, y);
            }
            xi = Math.cos(theta + dTheta) * rx + cx;
            yi = Math.sin(theta + dTheta) * ry + cy;
            break;
        case 'z':
            if (hasStroke) {
                if (isInsideLine(xi, yi, x0, y0, lineWidth, x, y)) {
                    return true;
                }
            }
            beginSubpath = true;
            break;
        }
    }
    if (hasFill) {
        w += windingLine(xi, yi, x0, y0, x, y);
    }
    return w !== 0;
}
/**
         * 测算多行文本宽度
         * @param {Object} text
         * @param {Object} textFont
         */
function getTextWidth(text, textFont) {
    var key = text + ':' + textFont;
    if (_textWidthCache[key]) {
        return _textWidthCache[key];
    }
    _ctx = _ctx || util.getContext();
    _ctx.save();
    if (textFont) {
        _ctx.font = textFont;
    }
    text = (text + '').split('\n');
    var width = 0;
    for (var i = 0, l = text.length; i < l; i++) {
        width = Math.max(_ctx.measureText(text[i]).width, width);
    }
    _ctx.restore();
    _textWidthCache[key] = width;
    if (++_textWidthCacheCounter > TEXT_CACHE_MAX) {
        // 内存释放
        _textWidthCacheCounter = 0;
        _textWidthCache = {};
    }
    return width;
}
/**
         * 测算多行文本高度
         * @param {Object} text
         * @param {Object} textFont
         */
function getTextHeight(text, textFont) {
    var key = text + ':' + textFont;
    if (_textHeightCache[key]) {
        return _textHeightCache[key];
    }
    _ctx = _ctx || util.getContext();
    _ctx.save();
    if (textFont) {
        _ctx.font = textFont;
    }
    text = (text + '').split('\n');
    // 比较粗暴
    var height = (_ctx.measureText('国').width + 2) * text.length;
    _ctx.restore();
    _textHeightCache[key] = height;
    if (++_textHeightCacheCounter > TEXT_CACHE_MAX) {
        // 内存释放
        _textHeightCacheCounter = 0;
        _textHeightCache = {};
    }
    return height;
}
module.exports = {
    isInside: isInside,
    isOutside: isOutside,
    getTextWidth: getTextWidth,
    getTextHeight: getTextHeight,
    isInsidePath: isInsidePath,
    isInsidePolygon: isInsidePolygon,
    isInsideSector: isInsideSector,
    isInsideCircle: isInsideCircle,
    isInsideLine: isInsideLine,
    isInsideRect: isInsideRect,
    isInsidePolyline: isInsidePolyline,
    isInsideCubicStroke: isInsideCubicStroke,
    isInsideQuadraticStroke: isInsideQuadraticStroke
} || module.exports;;